# Copyright (c) 2025, qiushike and contributors
# For license information, please see license.txt

import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import flt, now
from light_mes.api import get_cached_value
from frappe.query_builder import Order
from pypika.terms import Bracket


class JobCardScanInput(Document):
	@frappe.whitelist()
	def get_tracking_number(self):
		"""
		根据扫码或手输的至少最后5位追踪号获取追踪号信息
		"""

		def get_next_operation_message(tracking_number):
			"""
			返回下个工序的出错消息，正常就不返回出错消息
			"""
			my_open_job_cards, open_job_cards = self.get_tracking_number_open_job_cards(tracking_number)
			if not my_open_job_cards:
				if open_job_cards:
					return _("Tracking number not ready for this operation")
				elif not open_job_cards:
					return _("Tracking number is completed")								
			
		dt_tracking_number = frappe.qb.DocType("Tracking Number")
		dt_work_order = frappe.qb.DocType("Work Order")
		
		query = frappe.qb.from_(dt_tracking_number
		).join(dt_work_order
		).on(dt_tracking_number.work_order == dt_work_order.name
		).select(
			dt_tracking_number.name,
			dt_tracking_number.work_order,
			dt_tracking_number.is_parent_tracking_number,
			dt_work_order.status
		)
		
		valid_work_order_status = ["Submitted", "Not Started", "In Process"]

		tracking_number = self.tracking_number
		input_length = len(tracking_number)
		if input_length < 5:
			return _("Input tracking number length need to be at least 6")
		elif input_length < 10:
			# 手工输入追踪号尾号
			query = query.where(dt_tracking_number.tracking_number.like(f"%{tracking_number}"))
		else:
			query = query.where(dt_tracking_number.tracking_number==tracking_number)

		tracking_data = query.run(as_list=1)
		if not tracking_data:
			return (_("Tracking number {0} not exist").format(tracking_number))
		else:
			(tracking_number, work_order, is_parent_tracking_number, work_order_status) = tracking_data[0]
			self.tracking_number = tracking_number
			if work_order_status not in valid_work_order_status:
				return (_("Tracking number {0} work order {1} in non active status {2}").format(
					tracking_number, work_order, work_order_status))
		if not self.work_order:					
			self.work_order = work_order
		# 设置一个可复用的工单单据			
		self.set_work_order_doc()
		# 追踪号不属于当前工单，则返回工单号	
		if self.work_order != work_order:
			return  _("Wrong Work order {0}").format(work_order)
		# 检查是否属于当前工序
		existing_operation = self.operation
		if is_parent_tracking_number:
			child_tracking_numbers = frappe.get_all("Tracking Number", 
				{
					"parent_tracking_number": tracking_number
				},
				pluck="name"
			)
			for (i, child_tracking_number) in enumerate(child_tracking_numbers):
				# 子追踪号属于同一个工单，同一个主追踪号，工序进度状态一样，获取下个工序只要执行一次
				if not i:
					msg = get_next_operation_message(child_tracking_number)
					if msg:
						return msg
				if self.enable_tracking_detail:		
					self.append('tracking_details', {
						"tracking_number": child_tracking_number,
						"parent_tracking_number": tracking_number
					})	
		else:			
			msg = get_next_operation_message(tracking_number)
			if msg:
				return msg
			if self.enable_tracking_detail:
				self.append('tracking_details', {
					"tracking_number": tracking_number
				})
		self.set_completed_qty()

	def set_completed_qty(self):
		if self.tracking_details and self.conversion_factor:
			self.tracking_qty = len(self.tracking_details)
			self.completed_qty = self.tracking_qty * self.conversion_factor

	def set_work_order_doc(self):
		if not getattr(self, "work_order_doc", None):
			self.work_order_doc = frappe._dict()

	@frappe.whitelist()
	def get_work_order_next_operations(self):
		"""
		返回当前工单下一个或多个待完成工序
		"""
		if self.work_order:
			work_order_doc = frappe.get_doc("Work Order", self.work_order)
			self.conversion_factor = work_order_doc.get("meter_per_roll") or self.conversion_factor
			operations = [] 
			sequence_id = None
			for row in work_order_doc.operations:
				if not sequence_id:
					if (row.completed_qty + row.process_loss_qty) < work_order_doc.qty:
						operations.append(row.operation)
						if not self.operation:
							self.operation = row.operation
						elif self.operation != row.operation:
							work_order = self.work_order
							self.work_order=""
							frappe.throw(_(f"所选工单 {work_order} 当前工序 {row.operation} 与用户工序 {self.operation} 不匹配"))
							return
						operation_data = frappe.db.get_value("Operation", row.operation, 
							["dynamic_fields", "tracking_type"])
						if operation_data:
							(self.dynamic_fields, self.tracking_type) = operation_data
						job_card_data = frappe.db.get_value("Job Card", {
							"operation_id": row.name,
							"docstatus": 0
						}, ["name", "for_quantity"])
						if job_card_data:
							(self.job_card, self.for_quantity) = job_card_data					
						sequence_id = row.sequence_id
				else:
					if row.sequence_id == sequence_id:
						operations.append(row.operation)
					elif row.sequence_id > sequence_id:
						break
			self.set_completed_qty()

			return operations

	@frappe.whitelist()
	def get_tracking_number_open_job_cards(self, tracking_number):
		"""
		为跟踪单号匹配本人及整个跟踪号待报工生产任务单清单
		"""

		jc = frappe.qb.DocType("Job Card")
		jctl = frappe.qb.DocType("Job Card Time Log")
		jd = frappe.qb.DocType("Tracking Detail")
		
		# 本追踪号上一个已报工工序顺序号						
		existing_job_cards = frappe.qb.from_(jc
		).left_join(jd
		).on(jc.name == jd.parent
		).left_join(jctl
		).on(
			(jc.name == jctl.parent) &
			(jctl.employee == self.employee) &
			(jctl.parentfield == "employee")
		).select(
			jc.name.as_("job_card"),
			jc.tracking_number,
			jd.tracking_number.as_('detail_tracking_number'), 
			jctl.employee,
			jc.operation,
			jc.sequence_id,
			jc.operation_id,
			jc.for_quantity,
			jc.total_completed_qty,
			jc.status
		).where(
			(
				Bracket((jc.tracking_number == tracking_number) | 
				(jd.tracking_number == tracking_number))
			) & (jc.work_order == self.work_order) &
			(jc.docstatus < 2)
		).orderby(jc.sequence_id		#, order=Order.desc 不用倒序，用默认升序即可
		).run(as_dict = 1)

		my_allowed_operations = frappe.get_list("Operation", 
			filters = {"name": ("in", {r.operation for r in existing_job_cards})},
			pluck="name"
		)
		my_operations = [self.operation] if self.operation else my_allowed_operations
		
		def get_open_job_cards():
			for row in existing_job_cards:
				if row.status in ["Open", "Work In Progress", "On Hold", "Material Transferred"]:
					open_job_cards.append(row)
					if row.operation in my_operations and (row.employee == self.employee or not row.employee):
						my_open_job_cards.append(row)
		
		my_open_job_cards, open_job_cards = [], []
		get_open_job_cards()
		# 如果当前用户的第一个默认可报工的工序匹配不到可报工的生产任务单，匹配其它可报工的工序
		if not my_open_job_cards and self.operation and my_allowed_operations != [self.operation]:
			my_operations = [d for d in my_allowed_operations if d != self.operation]
			get_open_job_cards()

		if my_open_job_cards:
			job_card_data = my_open_job_cards[0]
							
			self.update(job_card_data)
			# 待办 本工序良品数	= 上工序顺序 按工序小计最小良品数 - 本工序已报工良品数+不良品数
			pending_complete_qty = self.get_last_operation_completed_qty(job_card_data, existing_job_cards)
			if pending_complete_qty:
				self.total_completed_qty = pending_complete_qty			
			self.total_completed_qty = self.total_completed_qty or self.for_quantity
			self.update(frappe.db.get_value("Operation", job_card_data.operation, 
				["name as opertion", "dynamic_fields", "tracking_type", "enable_tracking_detail"],
				as_dict=1
			))

		return (my_open_job_cards, open_job_cards)

	def get_last_operation_completed_qty(self, cur_job_card, existing_job_cards):
		"""
		获取上个工序顺序的已确认良品数为本工序默认报工数
		"""
		cur_sequence_id = cur_job_card.sequence_id
		cur_operation = cur_job_card.operation
		cur_operation_confirmed_qty = 0
		last_sequence_id = None
		last_operation_completed_qty_map = {}
		for row in reversed(existing_job_cards):
			if not last_sequence_id:
				if row.sequence_id > cur_sequence_id:
					continue
				elif row.sequence_id == cur_sequence_id:
					if row.job_card != cur_job_card.job_card and row.operation == cur_operation:
						cur_operation_confirmed_qty += flt(row.total_completed_qty) + flt(row.defect_qty)
				else:
					last_sequence_id = row.sequence_id
		
			if last_sequence_id:
				if row.sequence_id == last_sequence_id:
					last_operation_completed_qty_map.setdefault(row.operation, 0)
					last_operation_completed_qty_map[row.operation] += row.total_completed_qty
				elif row.sequence_id < last_sequence_id:
					break
		min_qty = min(last_operation_completed_qty_map.values()) if last_operation_completed_qty_map else 0

		return min_qty - cur_operation_confirmed_qty if min_qty else 0


	@frappe.whitelist()
	def update_job_card(self):		
		job_card_doc = frappe.get_doc("Job Card", self.job_card)
		
		if self.workstation:
			job_card_doc.workstation = self.workstation
		
		to_finish_job_card = False
		if job_card_doc.status == "Open":
			job_card_doc.time_logs = []
			time_logs_dict = {"from_time": now()}
			# 优先分组报工
			if self.employees:
				job_card_doc.employee = []
				for row in self.employees:
					job_card_doc.append("employee", {"employee": row.employee})	
			elif self.employee:
				time_logs_dict['employee'] = self.employee			
			job_card_doc.append("time_logs", time_logs_dict)			
			if get_cached_value("skip_job_card_start"):
				to_finish_job_card = True
			else:
				job_card_doc.save(ignore_permissions=True)
		elif job_card_doc.status in ["Work In Progress", "On Hold", "Material Transferred"]:
			to_finish_job_card = True

		if to_finish_job_card:
			if not job_card_doc.time_logs:
				job_card_doc.append("time_logs", {"completed_qty": self.total_completed_qty})		
			job_card_doc.time_logs[0].update(
				{
					"to_time": now(),
					"completed_qty": self.total_completed_qty
				}
			)
			if self.enable_tracking_detail:
				job_card_doc.tracking_details = []
				for row in self.tracking_details:
					job_card_doc.append("tracking_details", row)
				job_card_doc.service_price_base_qty = self.tracking_qty	
			else:
				job_card_doc.tracking_number = self.tracking_number
			
			job_card_doc.for_quantity = flt(self.total_completed_qty) + flt(self.defect_qty)
			job_card_doc.save(ignore_permissions=True)

			if job_card_doc.has_permission("submit"):
				job_card_doc.submit()

@frappe.whitelist()
def get_tracking_numbers(work_order, operation):
	#获取当前工序的上一个工序
	work_order_doc = frappe.get_doc("Work Order", work_order)
	prev_operation = None
	operation_in_work_order = False
	for (idx, row) in enumerate(work_order_doc.operations):
		if row.operation == operation:
			operation_in_work_order = True
			if idx:
				prev_operation = work_order_doc.operations[idx-1].operation
				break
	if not operation_in_work_order:
		frappe.throw(_("Operation {0} not in work order {1}").format(operation, work_order))
	elif not prev_operation:
		frappe.throw(_("Operation {0} in work order {1} is first operation, please go to job card directly").format(operation, work_order))
	elif prev_operation:
		# 获取前工序已报工，本工序尚未报工的追踪号
		jc_prev = frappe.qb.DocType("Job Card").as_("jc_prev")
		td_prev = frappe.qb.DocType("Tracking Detail").as_("td_prev")
		jc_cur = frappe.qb.DocType("Job Card").as_("jc_cur")
		td_cur = frappe.qb.DocType("Tracking Detail").as_("td_cur")

		sub_query = frappe.qb.from_(td_cur
		).join(jc_cur
		).on(jc_cur.name == td_cur.parent
		).where(			
			(jc_cur.work_order == work_order) &
			(jc_cur.docstatus<2) &
			(jc_cur.operation == operation) 			
		).select(td_cur.tracking_number)

		data = frappe.qb.from_(jc_prev
		).join(td_prev
		).on(jc_prev.name == td_prev.parent
		).where(
			(jc_prev.work_order == work_order) &
			(jc_prev.operation == prev_operation) &
			(jc_prev.docstatus == 1) &
			(td_prev.tracking_number.notin(sub_query))
		).select(td_prev.tracking_number
		).run(as_dict=1)

		return data
